package com.googlecode.contests_repo.core;

import java.io.File;
import java.util.List;
import java.util.regex.Pattern;

import org.codehaus.jackson.map.ObjectMapper;
import com.googlecode.contests_repo.core.config.ConfigurationManager;
import com.googlecode.contests_repo.core.model.*;

/**
 * 
 * @author joro, petko
 * 
 */
public class WebTools {
	private static final String resourceRegex = "[a-z0-9][a-z0-9_-]*";
	private static Pattern resourcePattern = Pattern.compile(resourceRegex);
	@SuppressWarnings("unused")
	private static String additionalFilesRegex = "_public_files|_files";

	/**
	 * Represents the types of resources in the system that can be managed -
	 * series, contests, problems, series lists and additional files.
	 * 
	 * @author joro, petko
	 * 
	 */
	public enum ResourceType {
		/**
		 * 
		 */
		SERIES_LIST("series_list"),
		/**
		 * 
		 */
		SERIES("series") {
			public String getOptionString() {
				return "-s";
			}
		},
		/**
		 * 
		 */
		CONTEST("contest") {
			public String getOptionString() {
				return "-c";
			}
		},
		/**
		 * 
		 */
		PROBLEM("problem") {
			public String getOptionString() {
				return "-p";
			}
		},
		/**
				 * 
				 */
		ADDITIONAL_FILE("view_folder");

		private String resourceTypeName;

		ResourceType(String resourceTypeName) {
			this.resourceTypeName = resourceTypeName;
		}

		/**
		 * 
		 * @return The resource type name
		 */
		public String getResourceTypeName() {
			return this.resourceTypeName;
		}

		/**
		 * 
		 * @return The successor type of this resource type
		 */
		public ResourceType getSuccessor() {
			switch (this) {
			case SERIES_LIST:
				return SERIES;
			case SERIES:
				return CONTEST;
			case CONTEST:
				return PROBLEM;
			case PROBLEM:
				return null;
			}

			return null;
		}

		/**
		 * 
		 * @return The option string
		 */
		public String getOptionString() {
			return "";
		}
	}

	/**
	 * Given a resource path relative to the contest repo root the method
	 * calculates what type of resource the path refers to.
	 * 
	 * @param path
	 * @return Resource type of the specified path
	 */
	public static ResourceType getResourceType(String path) {
		ResourceType[] results = { ResourceType.SERIES, ResourceType.CONTEST,
				ResourceType.PROBLEM };
		if (path.equals(ConfigurationManager.getInstance()
				.getSeriesRelativePath())) {
			return ResourceType.SERIES_LIST;
		}

		for (int i = 0; i < 3; i++) {
			path = path.substring(path.indexOf(File.separator) + 1);
			if (resourcePattern.matcher(path).matches()) {
				return results[i];
			} else if (path.startsWith("_public_files")
					|| path.startsWith("_files")) {
				return ResourceType.ADDITIONAL_FILE;
			}
		}

		path = path.substring(path.indexOf(File.separator) + 1);
		if (path.startsWith("_public_files") || path.startsWith("_files")) {
			return ResourceType.ADDITIONAL_FILE;
		}
		return null;
	}

	/**
	 * Given a resource path relative to the contest repo root the method
	 * calculates what kind of .jsp page will handle the request corresponding
	 * to the path.
	 * 
	 * @param path
	 *            path to a resource relative to the contest reo root
	 * @return the name of the .jsp page that handles the path request
	 */
	public static String getResourceHandler(String path) {
		ResourceType resourceType = getResourceType(path);
		if (resourceType == null) {
			resourceType = ResourceType.SERIES_LIST;
		}
		return String.format("/%s.jsp", resourceType.getResourceTypeName());
	}

	/**
	 * Given a relative resource path calculates a relative parent resource path
	 * 
	 * @param resourcePath
	 *            relative path to a resource
	 * @return the relative path to the parent resource
	 */
	public static String getRelativeParentPath(String resourcePath) {
		return resourcePath.substring(0,
				resourcePath.lastIndexOf(File.separator));
	}

	private static String createJsonFile(ResourceType type, String resourcePath) {
		ObjectMapper mapper = new ObjectMapper();
		Resource resource = null;
		switch (type) {
		case SERIES:
			resource = new Series();
			break;
		case CONTEST:
			resource = new Contest();
			break;
		case PROBLEM:
			resource = new Problem();
			break;
		}
		try {
			mapper.writeValue(new File(resourcePath, type.getResourceTypeName()
					+ ".json"), resource);
		} catch (Exception e) {
			return "Unable to create json file";
		}
		return "";
	}

	/**
	 * Creates an empty resource(folder) with the specified name in the folder
	 * relativeParentPath. Also adds a corresponding .json file.
	 * 
	 * @param relativeParentPath
	 * @param resourceName
	 * @return the error message if an error occurred or an empty string
	 *         otherwise
	 */
	public static String createEmptyResource(String relativeParentPath,
			String resourceName) {
		if (resourceName == null || resourceName.length() == 0) {
			return "The name of the new resource cannot be empty";
		}
		if (!resourcePattern.matcher(resourceName).matches()) {
			return "The name of the new resource is not valid";
		}
		String repoRoot = ConfigurationManager.getInstance().getRepoRoot();
		String newResourceDir = relativeParentPath + File.separator
				+ resourceName;
		File resourceFile = new File(repoRoot + File.separator + newResourceDir);
		if (resourceFile.exists()) {
			return "A resource with that name already exists";
		}
		if (!resourceFile.mkdir()) {
			return "Unable to create resource";
		}
		return createJsonFile(getResourceType(newResourceDir), repoRoot
				+ File.separator + newResourceDir);
	}

	/**
	 * Formats a message to be represented in an html. The text is colored
	 * accordingly.
	 * 
	 * @param color
	 *            the name of the color the text message will be in
	 * @param message
	 *            the message that will be formatted
	 * @return the HTML formatted color message
	 */
	public static String getFormatMessage(String color, String message) {
		return String.format("<div style='color:%s'>%s</div>", color, message);
	}

	/**
	 * Splits a string into an array of trimmed tokens.
	 * 
	 * @param string
	 *            the string that will be split
	 * @return the array of trimmed tokens corresponding to the passed string
	 */
	public static String[] stringToArray(String string) {
		if (isNullOrEmpty(string)) {
			return null;
		}
		string = string.trim();
		String[] substrings = string.split(",");
		for (int i = 0; i < substrings.length; i++) {
			substrings[i] = substrings[i].trim();
		}

		return substrings;
	}

	/**
	 * Given a string representation of a delimited sequence of integers returns
	 * an array of the integers.
	 * 
	 * @param string
	 *            the string of integers to be split
	 * @return the array of integers represented by the string
	 */
	public static int[] integerStringToArray(String string) {
		if (isNullOrEmpty(string)) {
			return null;
		}

		String[] substrings = stringToArray(string);
		int[] result = new int[substrings.length];
		for (int i = 0; i < substrings.length; i++) {
			result[i] = Integer.parseInt(substrings[i]);
		}

		return result;
	}

	/**
	 * Converts an array of values into a delimited string
	 * 
	 * @param <T>
	 *            the type of the element in the passed array
	 * @param array
	 *            the array of values that will be converted
	 * @return the delimited string of array values
	 */
	public static <T> String arrayToString(T[] array) {
		if (array == null) {
			return "";
		}
		StringBuilder result = new StringBuilder();
		for (int i = 0; i < array.length; i++) {
			result.append(array[i]);
			if (i < array.length - 1) {
				result.append(", ");
			}
		}
		return result.toString();
	}

	/**
	 * Converts an array of values into a delimited string
	 * 
	 * @param array
	 *            the array of values that will be converted
	 * @return the delimited string of array values
	 */
	public static String arrayToString(int[] array) {
		if (array == null) {
			return "";
		}
		StringBuilder result = new StringBuilder();
		for (int i = 0; i < array.length; i++) {
			result.append(array[i]);
			if (i < array.length - 1) {
				result.append(", ");
			}
		}
		return result.toString();
	}

	/**
	 * Checks whether the specified string is null or has an empty value.
	 * 
	 * @param string
	 *            the string to be checked
	 * @return true if the string is null or empty
	 */
	public static boolean isNullOrEmpty(String string) {
		return string == null || "".equals(string);
	}

	/**
	 * Given a list of error messages the method builds a formatted HTML string
	 * to be presented on a web page.
	 * 
	 * @param errors
	 *            the error messages to be formatted
	 * @param addSuccessMessage
	 *            indicates whether the success message should be included
	 * @return the HTML formatted string of messages
	 */
	public static String buildErrorString(List<String> errors,
			boolean addSuccessMessage) {
		return buildErrorString(errors, "Resource changed successfully",
				addSuccessMessage);
	}

	/**
	 * Given a list of error messages the method builds a formatted HTML string
	 * to be presented on a web page.
	 * 
	 * @param errors
	 *            the error messages to be formatted
	 * @param successMessage
	 *            the success message to be formatted in case the error list is
	 *            empty
	 * @param addSuccessMessage
	 *            indicates whether the success message should be included
	 * @return the HTML formatted string of messages
	 */
	public static String buildErrorString(List<String> errors,
			String successMessage, boolean addSuccessMessage) {
		StringBuilder stringBuilder = new StringBuilder();
		for (String error : errors) {
			stringBuilder.append(WebTools.getFormatMessage("red", error));
		}
		if (addSuccessMessage && stringBuilder.length() < 1) {
			stringBuilder.append(WebTools.getFormatMessage("green",
					successMessage));
		}

		return stringBuilder.toString();
	}
}
